package renderer;

/**
 * @author Eamon Gaffney
 * A general purpose matrix class for mathematical programming.
 */
public class Matrix {
	private int rows;
	private int columns;
	private double[][] data;
	
	/**
	 * @param rows
	 * @param columns
	 */
	public Matrix(int rows, int columns){
		this.rows = rows;
		this.columns= columns;
		this.data = new double[rows][columns];
	}
	
	/**
	 * @param vals data for the matrix in row major order
	 */
	public Matrix(double[][] vals){
		this.rows = vals.length;
		this.columns = vals[0].length;
		this.data = vals;
	}
	
	/**
	 * Generates an identity matrix.
	 * @param size the number of rows and columns
	 * @return identity matrix
	 */
	public static Matrix identity(int size){
		Matrix temp = new Matrix(size, size);
		for (int i = 0; i < size; i++){
			temp.setCell(i, i, 1);
		}
		return temp;
	}
	
	/**
	 * Gives the matrix new values.
	 * @param vals new matrix data
	 */
	public void setEntireMatrix(double[][] vals){
		this.data = vals;
	}
	
	public void setEntireMatrix(Matrix vals){
		if (this.rows != vals.rows || this.columns != vals.columns){
			throw new IncompatibleMatrixSizeException();
		}
		this.data = vals.data;
	}
	
	/**
	 * Changes one row of this matrix.
	 * @param row specify which row
	 * @param vals new data
	 */
	public void setRow(int row, double[] vals){
		if (this.columns != vals.length){
			throw new IncompatibleMatrixSizeException();
		}
		this.data[row] = vals;
	}
	
	/**
	 * Replaces one row of an n by 4 matrix with a vector.
	 * @param row specify which row
	 * @param vals new data
	 */
	public void setRow(int row, Vector4 vals){
		if (this.columns != 4){
			throw new IncompatibleMatrixSizeException();
		}
		for (int i = 0; i < 4; i++){
			this.setCell(row, i, vals.getCell(i, 0));
		}
	}
	
	/**
	 * Changes one column of this matrix.
	 * @param column specify which column
	 * @param vals new data
	 */
	public void setColumn(int column, double[] vals){
		if (this.rows != vals.length){
			throw new IncompatibleMatrixSizeException();
		}
		for (int i = 0; i < this.rows; i++){
			this.data[i][column] = vals[i];
		}
	}
	
	/**
	 * Replaces one column of a 4 by n matrix with a vector.
	 * @param column specify which column
	 * @param vals new data
	 */
	public void setColumn(int column, Vector4 vals){
		if (this.rows != 4){
			throw new IncompatibleMatrixSizeException();
		}
		for (int i = 0; i < 4; i++){
			this.setCell(i, column, vals.getCell(i, 0));
		}
	}
	
	/**
	 * Changes a single cell of the matrix.
	 * @param row row of the cell
	 * @param column column of the cell
	 * @param val new value
	 */
	public void setCell(int row, int column, double val){
		this.data[row][column] = val;
	}
	
	/**
	 * Returns the entire matrix as a 2D array of doubles
	 * @return the matrix data in row major format
	 */
	public double[][] getEntireMatrix(){
		return this.data;
	}
	
	/**
	 * Gets a single row of the matrix.
	 * @param row specify which row
	 * @return the data from the row
	 */
	public double[] getRow(int row){
		return this.data[row];
	}
	
	/**
	 * Takes data from a single row of an n by 4 matrix and puts it into a Vector4
	 * @param row specify which row
	 * @return new vector
	 */
	public Vector4 getRowAsVector(int row){
		Vector4 temp = new Vector4();
		temp.setColumn(0, this.getRow(row));
		return temp;
	}
	
	/**
	 * Gets a single column of the matrix.
	 * @param column specify which column
	 * @return the data from the column
	 */
	public double[] getColumn(int column){
		double[] temp = new double[rows];
		for (int i = 0; i < this.rows; i++){
			temp[i] = this.data[i][column];
		}
		return temp;
	}
	
	/**
	 * Takes data from a single row of an n by 4 matrix and puts it into a Vector4
	 * @param row specify which row
	 * @return new vector
	 */
	public Vector4 getColumnAsVector(int column){
		Vector4 temp = new Vector4();
		temp.setColumn(0, this.getColumn(column));
		return temp;
	}
	
	public double getCell(int row, int column){
		return this.data[row][column];
	}
	
	public int getNumRows(){
		return this.rows;
	}
	
	public int getNumColumns(){
		return this.columns;
	}
	
	public Matrix copy(){
		return this;
	}
	
	//Scales the entire matrix in place by a given value
	public void inPlaceScale(double val){
		for (int i = 0; i < this.rows; i++){
			for (int j = 0; j < this.columns; j++){
				this.data[i][j] *= val;
			}
		}
	}
	
	public Matrix scale(double val){
		Matrix temp = new Matrix(this.rows, this.columns);
		for (int i = 0; i < this.rows; i++){
			for (int j = 0; j < this.columns; j++){
				this.data[i][j] *= val;
			}
		}
		return temp;
	}
	
	//Adds two matrices
	public Matrix add(Matrix other){
		if (this.rows != other.rows || this.columns != other.columns){
			throw new IncompatibleMatrixSizeException();
		}
		Matrix temp = new Matrix(this.rows, this.columns);
		for (int i = 0; i < this.rows;i++){
			for (int j = 0; j < this.columns; j++){
				temp.data[i][j] = this.data[i][j] + other.data[i][j];
			}
		}
		return temp;
	}
	
	//Adds another matrix to this in place
	public void inPlaceAdd(Matrix other){
		if (this.rows != other.rows || this.columns != other.columns){
			throw new IncompatibleMatrixSizeException();
		}
		for (int i = 0; i < this.rows;i++){
			for (int j = 0; j < this.columns; j++){
					this.data[i][j] += other.data[i][j];
			}
		}
	}
	
	//Subtracts other from this
	public Matrix subtract(Matrix other){
		if (this.rows != other.rows || this.columns != other.columns){
			throw new IncompatibleMatrixSizeException();
		}
		Matrix temp = new Matrix(this.rows, this.columns);
		for (int i = 0; i < this.rows;i++){
			for (int j = 0; j < this.columns; j++){
				temp.data[i][j] = this.data[i][j] - other.data[i][j];
			}
		}
		return temp;
	}
	
	//Subtracts other from this in place
	public void inPlaceSubtract(Matrix other){
		if (this.rows != other.rows || this.columns != other.columns){
			throw new IncompatibleMatrixSizeException();
		}
		for (int i = 0; i < this.rows;i++){
			for (int j = 0; j < this.columns; j++){
					this.data[i][j] -= other.data[i][j];
			}
		}
	}
	
	//Multiplies two matrices
	public Matrix multiply(Matrix other){
		if (this.columns != other.rows){
			throw new IncompatibleMatrixSizeException();
		}
		Matrix temp = new Matrix(this.rows, other.columns);
		for (int i = 0; i < this.rows; i++){
			for (int j = 0; j < other.columns; j++){
				int sum = 0;
				for (int k = 0; k < this.columns; k++){
					sum += this.data[i][k] * other.data[k][j];
				}
				temp.data[i][j] = sum;
			}
		}
		return temp;
	}
	
	//Multiplies this by other in place
	public void inPlaceMltiply(Matrix other){
		if (this.columns != other.rows){
			throw new IncompatibleMatrixSizeException();
		}
		Matrix temp = new Matrix(this.rows, other.columns);
		for (int i = 0; i < this.rows; i++){
			for (int j = 0; j < other.columns; j++){
				int sum = 0;
				for (int k = 0; k < this.columns; k++){
					sum += this.data[i][k] * other.data[k][j];
				}
				temp.data[i][j] = sum;
			}
		}
		this.data = temp.data;
	}
}
